{{!

  Copyright (c) Facebook, Inc. and its affiliates.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.

}}

{{#program:services}}

    pub struct {{service:name}}Impl<P, T> {
        {{#service:extends}}
        parent: {{service:package}}::client::{{service:name}}Impl<P, T>,
        {{/service:extends}}
        {{^service:extends}}
        transport: T,
        _phantom: ::std::marker::PhantomData<fn() -> P>,
        {{/service:extends}}
    }

    impl<P, T> {{service:name}}Impl<P, T> {
        pub fn new(
            transport: T,
        ) -> Self {
            {{#service:extends}}
            let parent = {{service:package}}::client::{{service:name}}Impl::<P, T>::new(transport);
            Self { parent }
            {{/service:extends}}
            {{^service:extends}}
            Self {
                transport,
                _phantom: ::std::marker::PhantomData,
            }
            {{/service:extends}}
        }

        pub fn transport(&self) -> &T {
            {{#service:extends}}
            self.parent.transport()
            {{/service:extends}}
            {{^service:extends}}
            &self.transport
            {{/service:extends}}
        }
    }

    {{#service:extendedServices}}
    impl<P, T> {{!
        }}{{#extendedService:service}}{{!
        }}::std::convert::AsRef<dyn {{extendedService:packagePrefix}}::client::{{service:name}} + 'static> {{!
        }}{{/extendedService:service}}{{!
        }}for {{service:name}}Impl<P, T>
    where
        P: ::fbthrift::Protocol,
        T: ::fbthrift::Transport,
        {{! require P::Frame and T to have compatible DecBuf and EncBuf::Final }}
        P::Frame: ::fbthrift::Framing<DecBuf = ::fbthrift::FramingDecoded<T>>,
        ::fbthrift::ProtocolEncoded<P>: ::fbthrift::BufMutExt<Final = ::fbthrift::FramingEncodedFinal<T>>,
        P::Deserializer: ::std::marker::Send,
    {
        fn as_ref(&self) -> &(dyn {{#extendedService:service}}{{!
            }}{{extendedService:packagePrefix}}::client::{{service:name}}{{!
            }}{{/extendedService:service}}{{!
            }} + 'static)
        {
            {{extendedService:asRefImpl}}
        }
    }

    {{/service:extendedServices}}
    {{#service:docs?}}
    #[doc = {{service:docs}}]
    {{/service:docs?}}
    pub trait {{service:name}}: {{!
        }}{{#service:extends}}{{service:package}}::client::{{service:name}} + {{/service:extends}}{{!
        }}::std::marker::Send {{>lib/block}}{{!

        }}{{#service:rustFunctions}}
        {{^function:starts_interaction?}}
        {{#function:docs?}}
        #[doc = {{function:docs}}]
        {{/function:docs?}}
        fn {{function:rust_name}}(
            &self,
            {{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},
            {{/function:args}}
        ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::std::result::Result<{{!
            }}{{#function:return_type}}{{>lib/type}}{{/function:return_type}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + ::std::marker::Send + 'static>>;{{!
        }}{{/function:starts_interaction?}}{{!
        }}{{/service:rustFunctions}}
    }

    impl<P, T> {{service:name}} for {{service:name}}Impl<P, T>
    where
        P: ::fbthrift::Protocol,
        T: ::fbthrift::Transport,
        {{! require P::Frame and T to have compatible DecBuf and EncBuf::Final }}
        P::Frame: ::fbthrift::Framing<DecBuf = ::fbthrift::FramingDecoded<T>>,
        ::fbthrift::ProtocolEncoded<P>: ::fbthrift::BufMutExt<Final = ::fbthrift::FramingEncodedFinal<T>>,
        P::Deserializer: ::std::marker::Send,
    {{>lib/block}}{{!
        }}{{#service:rustFunctions}}
        {{^function:starts_interaction?}}

        #[::tracing::instrument(name = "{{service:name}}.{{function:name}}", skip_all)]
        fn {{function:rust_name}}(
            &self,{{!
            }}{{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},{{!
            }}{{/function:args}}
        ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::std::result::Result<{{!
            }}{{#function:return_type}}{{>lib/type}}{{/function:return_type}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + ::std::marker::Send + 'static>> {
            use ::const_cstr::const_cstr;
            use ::fbthrift::{ProtocolWriter as _};
            use ::futures::future::{FutureExt as _, TryFutureExt as _};
            use ::tracing::Instrument as _;
            const_cstr! {
                SERVICE_NAME = "{{service:name}}";
                METHOD_NAME = "{{service:name}}.{{function:name}}";
            }
            let request = ::tracing::trace_span!("serialize_args").in_scope(|| {
                ::fbthrift::serialize!(P, |p| ::fbthrift::protocol::write_message(
                    p,
                    "{{function:name}}",
                    ::fbthrift::MessageType::Call,
                    // Note: we send a 0 message sequence ID from clients because
                    // this field should not be used by the server (except for some
                    // language implementations).
                    0,
                    |p| {
                        p.write_struct_begin("args");{{!
                        }}{{#function:args}}
                        p.write_field_begin({{!
                            }}"arg_{{field:name}}", {{!
                            }}{{#field:type}}{{>lib/ttype}}{{/field:type}}, {{!
                            }}{{field:key}}i16{{!
                        }});
                        {{#field:type}}{{>lib/write}}{{/field:type}}(&arg_{{field:name}}, p);
                        p.write_field_end();{{!
                        }}{{/function:args}}
                        p.write_field_stop();
                        p.write_struct_end();
                    },
                ))
            });
            {{#function:returns_streams?}}
            use futures::StreamExt;

            self.transport()
                .call_stream(SERVICE_NAME, METHOD_NAME, request)
                .instrument(::tracing::info_span!("call_stream", method = "{{service:name}}.{{function:name}}"))
                .map_err(::std::convert::From::from)
                .and_then(|({{^function:stream_has_first_response?}}_{{/function:stream_has_first_response?}}initial_response, stream)|
                    ::tracing::trace_span!("deserialize_result").in_scope(|| {
                    let new_stream: {{#function:return_type}}::std::pin::Pin<::std::boxed::Box<dyn ::futures::stream::Stream<
                    Item = ::std::result::Result<{{#type:stream_elem_type}}{{>lib/type}}{{/type:stream_elem_type}}, {{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}StreamError>> + ::std::marker::Send + 'static >>{{/function:return_type}} = ::std::boxed::Box::pin(stream.then(
                        |stream_item_response| {
                            match stream_item_response {
                                Err(e) => {
                                    ::futures::future::Either::Left(
                                        ::futures::future::ready(
                                            ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}StreamError::from(e))
                                        )
                                    )
                                },
                                Ok(stream_item) => {
                                    let de = P::deserializer(stream_item);
                                    (move |mut p: P::Deserializer| {
                                        let exn: ::tokio_shim::task::JoinHandle<(Result<{{program:crate}}::services::{{service:snake}}::{{function:upcamel}}StreamExn, _>, _)> =
                                            ::tokio_shim::task::spawn_blocking_fallback_inline(|| (::fbthrift::Deserialize::read(&mut p), p));
                                        ::futures::future::Either::Right(exn.then(
                                            |exn| {
                                                let result = (move || {
                                                    let (exn, _) = match exn {
                                                      Ok(res) => res,
                                                      Err(e) => {
                                                          // spawn_blocking threads can't be cancelled, so any
                                                          // error is a panic. This shouldn't happen, but we propagate if it does
                                                          ::std::panic::resume_unwind(e.into_panic())
                                                      }
                                                  };
                                                  let exn = exn?;
                                                  let result = match exn {
                                                      {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}StreamExn::Success(x) => ::std::result::Result::Ok(x),
                                                      {{#function:stream_exceptions}}
                                                      {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}StreamExn::{{field:name}}(err) => {
                                                          ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}StreamError::{{field:name}}(err))
                                                      }
                                                      {{/function:stream_exceptions}}
                                                      {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}StreamExn::ApplicationException(ae) => {
                                                          ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}StreamError::ApplicationException(ae))
                                                      }
                                                  };
                                                  result
                                                })();
                                                ::futures::future::ready(result)
                                              }))
                                    })(de)
                                }
                            }
                        }
                    ));
                    {{#function:stream_has_first_response?}}
                    let de = P::deserializer(initial_response);
                    (move |mut p: P::Deserializer| {
                        use ::fbthrift::{ProtocolReader as _};
                        let (_, message_type, _) = match p.read_message_begin(|_| ()) {
                            Ok(res) => res,
                            Err(e) => return ::futures::future::Either::Left(
                                    ::futures::future::ready(
                                        ::std::result::Result::Err(e.into())
                                    )
                                )
                        };
                        match message_type {
                            ::fbthrift::MessageType::Reply => {
                                let exn: ::tokio_shim::task::JoinHandle<(Result<{{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn, _>, _)> = ::tokio_shim::task::spawn_blocking_fallback_inline(move || {
                                  (::fbthrift::Deserialize::read(&mut p), p)
                                });
                                ::futures::future::Either::Right(exn.then(
                                    |exn| {
                                        let result = (move || {
                                            let (exn, mut p) = match exn {
                                                Ok(res) => res,
                                                Err(e) => {
                                                    // spawn_blocking threads can't be cancelled, so any
                                                    // error is a panic. This shouldn't happen, but we propagate if it does
                                                    ::std::panic::resume_unwind(e.into_panic())
                                                }
                                            };
                                            let exn = exn?;
                                            let result = match exn {
                                                {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::Success(x) => ::std::result::Result::Ok(
                                                    (x, new_stream)
                                                ),
                                                {{#function:exceptions}}
                                                {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::{{field:name}}(err) => {
                                                    ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::{{field:name}}(err))
                                                }
                                                {{/function:exceptions}}
                                                {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::ApplicationException(ae) => {
                                                    ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::ApplicationException(ae))
                                                }
                                            };
                                            p.read_message_end()?;
                                            result
                                        })();
                                        ::futures::future::ready(result)
                                    }
                                ))
                            }
                            ::fbthrift::MessageType::Exception => {
                                let ae: ::std::result::Result<::fbthrift::ApplicationException, _> = ::fbthrift::Deserialize::read(&mut p);
                                ::futures::future::Either::Left(
                                    ::futures::future::ready(
                                        ae.map_err(|e| e.into()).and_then(|ae| {
                                        p.read_message_end().map_err(|e| e.into()).and_then(
                                          |_| {
                                              ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::ApplicationException(ae))
                                          }
                                        )
                                        })
                                    )
                                )
                            }
                            ::fbthrift::MessageType::Call | ::fbthrift::MessageType::Oneway | ::fbthrift::MessageType::InvalidMessageType => {
                                let err = ::anyhow::anyhow!("Unexpected message type {:?}", message_type);
                                ::futures::future::Either::Left(
                                    ::futures::future::ready(
                                        p.read_message_end().map_err(|e| e.into()).and_then(
                                          |_| {
                                            ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::ThriftError(err))
                                          }
                                        )
                                    )
                                )
                            }
                        }
                    })(de)
                    {{/function:stream_has_first_response?}}
                    {{^function:stream_has_first_response?}}
                    ::futures::future::ready(::std::result::Result::Ok(new_stream))
                    {{/function:stream_has_first_response?}}
                }))
                .boxed()
            {{/function:returns_streams?}}{{^function:returns_streams?}}
            self.transport()
                .call(SERVICE_NAME, METHOD_NAME, request)
                .instrument(::tracing::info_span!("call", function = "{{service:name}}.{{function:name}}"))
                .map_err(::std::convert::From::from)
                .and_then(|reply| {
                        let de = P::deserializer(reply);
                        (move |mut p: P::Deserializer| {
                            use ::fbthrift::{ProtocolReader as _};
                            let (_, message_type, _) = match p.read_message_begin(|_| ()) {
                                Ok(res) => res,
                                Err(e) => {
                                    ::tracing::error!(error = ?e);
                                    return ::futures::future::Either::Left(
                                        ::futures::future::ready(
                                            ::std::result::Result::Err(e.into())
                                        )
                                    )
                                }
                            };
                            match message_type {
                                ::fbthrift::MessageType::Reply => {
                                    let exn: ::tokio_shim::task::JoinHandle<(Result<{{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn, _>, _)> =
                                        ::tokio_shim::task::spawn_blocking_fallback_inline(move || (::fbthrift::Deserialize::read(&mut p), p));
                                    ::futures::future::Either::Right(exn.then(
                                        |exn| {
                                            let result = (move || {
                                                let (exn, mut p) = match exn {
                                                    Ok(res) => res,
                                                    Err(e) => {
                                                        // spawn_blocking threads can't be cancelled, so any
                                                        // error is a panic. This shouldn't happen, but we propagate if it does
                                                        ::std::panic::resume_unwind(e.into_panic())
                                                    }
                                                };
                                                let exn = exn?;
                                                let result = match exn {
                                                    {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::Success(x) => {
                                                        ::tracing::info!("success");
                                                        ::std::result::Result::Ok(x)
                                                    },
                                                    {{#function:exceptions}}
                                                    {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::{{field:name}}(err) => {
                                                        ::tracing::error!(?err);
                                                        ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::{{field:name}}(err))
                                                    }
                                                    {{/function:exceptions}}
                                                    {{program:crate}}::services::{{service:snake}}::{{function:upcamel}}Exn::ApplicationException(ae) => {
                                                        ::tracing::error!(application_exception = ?ae);
                                                        ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::ApplicationException(ae))
                                                    }
                                                };
                                                p.read_message_end()?;
                                                result
                                            })();
                                            ::futures::future::ready(result)
                                        }
                                    ))
                                }
                                ::fbthrift::MessageType::Exception => {
                                    let ae: ::std::result::Result<::fbthrift::ApplicationException, _> = ::fbthrift::Deserialize::read(&mut p);
                                    ::futures::future::Either::Left(
                                        ::futures::future::ready(
                                            ae.map_err(|e| e.into()).and_then(|ae| {
                                                p.read_message_end().map_err(|e| e.into()).and_then(|_| {
                                                    ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::ApplicationException(ae))
                                                })
                                            })
                                        )
                                    )
                                }
                                ::fbthrift::MessageType::Call | ::fbthrift::MessageType::Oneway | ::fbthrift::MessageType::InvalidMessageType => {
                                    let err = ::anyhow::anyhow!("Unexpected message type {:?}", message_type);
                                    ::futures::future::Either::Left(
                                        ::futures::future::ready(
                                            p.read_message_end().map_err(|e| e.into()).and_then(
                                            |_| {
                                                ::std::result::Result::Err({{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error::ThriftError(err))
                                            }
                                            )
                                        )
                                    )
                                }
                            }
                        })(de)
                    }
                    .instrument(::tracing::trace_span!("deserialize_response", method = "{{service:name}}.{{function:name}}"))
                )
                .boxed()
        {{/function:returns_streams?}}
        }
        {{/function:starts_interaction?}}
        {{/service:rustFunctions}}
    }

    impl<'a, T> {{service:name}} for T
    where
        T: ::std::convert::AsRef<dyn {{service:name}} + 'a>,
        {{#service:extendedServices}}
        {{#extendedService:service}}
        T: {{extendedService:packagePrefix}}::client::{{service:name}},
        {{/extendedService:service}}
        {{/service:extendedServices}}
        T: ::std::marker::Send,
    {
        {{#service:rustFunctions}}
        {{^function:starts_interaction?}}
        fn {{function:rust_name}}(
            &self,{{!
            }}{{#function:args}}
            arg_{{field:name}}: {{>lib/arg}},{{!
            }}{{/function:args}}
        ) -> ::std::pin::Pin<::std::boxed::Box<dyn ::std::future::Future<Output = ::std::result::Result<{{!
            }}{{#function:return_type}}{{>lib/type}}{{/function:return_type}}, {{!
            }}{{program:crate}}::errors::{{service:snake}}::{{function:upcamel}}Error{{!
        }}>> + ::std::marker::Send + 'static>> {
            self.as_ref().{{function:rust_name}}(
                {{#function:args}}
                arg_{{field:name}},
                {{/function:args}}
            )
        }
        {{/function:starts_interaction?}}
        {{/service:rustFunctions}}
    }

    #[derive(Clone)]
    pub struct make_{{service:name}};

    /// To be called by user directly setting up a client. Avoids
    /// needing ClientFactory trait in scope, avoids unidiomatic
    /// make_Trait name.
    ///
    /// ```
    /// # const _: &str = stringify! {
    /// use bgs::client::BuckGraphService;
    ///
    /// let protocol = BinaryProtocol::new();
    /// let transport = HttpClient::new();
    /// let client = <dyn BuckGraphService>::new(protocol, transport);
    /// # };
    /// ```
    impl dyn {{service:name}} {
        {{#service:annotations}}
        {{#annotation:value?}}
        pub const {{annotation:rust_name}}: &'static ::std::primitive::str = {{annotation:rust_value}};
        {{/annotation:value?}}
        {{/service:annotations}}
        pub fn new<P, T>(
            protocol: P,
            transport: T,
        ) -> ::std::sync::Arc<impl {{service:name}} + ::std::marker::Send + 'static>
        where
            P: ::fbthrift::Protocol<Frame = T>,
            T: ::fbthrift::Transport,
            P::Deserializer: ::std::marker::Send,
        {
            let _ = protocol;
            ::std::sync::Arc::new({{service:name}}Impl::<P, T>::new(transport))
        }
    }

    pub type {{service:name}}DynClient = <make_{{service:name}} as ::fbthrift::ClientFactory>::Api;
    pub type {{service:name}}Client = ::std::sync::Arc<{{service:name}}DynClient>;

    /// The same thing, but to be called from generic contexts where we are
    /// working with a type parameter `C: ClientFactory` to produce clients.
    impl ::fbthrift::ClientFactory for make_{{service:name}} {
        type Api = dyn {{service:name}} + ::std::marker::Send + ::std::marker::Sync + 'static;

        fn new<P, T>(protocol: P, transport: T) -> ::std::sync::Arc<Self::Api>
        where
            P: ::fbthrift::Protocol<Frame = T>,
            T: ::fbthrift::Transport + ::std::marker::Sync,
            P::Deserializer: ::std::marker::Send,
        {
            <dyn {{service:name}}>::new(protocol, transport)
        }
    }{{!
}}{{/program:services}}


{{!newline}}
